﻿using System;
using System.Windows.Media;
using Black.SurfaceToolkit.Tools;

namespace Black.SurfaceToolkit
{
    /// <summary>
    /// Fournit des méthodes d'aide aux calculs mathématiques.
    /// </summary>
    public static class MathHelper
    {
        #region Fonctions statiques publiques

        /// <summary>
        /// Calcule la transformation manquante entre les deux transformations spécifiées en tant qu'instance de <see cref="T:System.Windows.Media.TransformGroup" /> composée d'un <see cref="T:System.Windows.Media.ScaleTransform" />, d'un <see cref="T:System.Windows.Media.SkewTransform" />, d'un <see cref="T:System.Windows.Media.RotateTransform" /> puis d'un <see cref="T:System.Windows.Media.TranslateTransform" />.
        /// </summary>
        /// <param name="p_oSourceToRootTransform">Transformation de passage d'un point de la source à un point de la racine visuelle.</param>
        /// <param name="p_oDestinationToRootTransform">Transformation de passage d'un point de la destination à un point de la racine visuelle. Doit être inversible.</param>
        /// <param name="p_eSkewMode">Mode d'inclinaison de la décomposition.</param>
        /// <returns>Transformation manquante entre <paramref name="p_oSourceToRootTransform" /> et <paramref name="p_oDestinationToRootTransform" />.</returns>
        public static TransformGroup BuildDeltaTransformation(Transform p_oSourceToRootTransform, Transform p_oDestinationToRootTransform, DecompositionSkewMode p_eSkewMode)
        {
            TransformGroup oRawTransfromGroup = new TransformGroup()
            {
                Children = new TransformCollection()
                {
                    p_oSourceToRootTransform,
                    (Transform)p_oDestinationToRootTransform.Inverse,
                }
            };

            Matrix oMatrix = oRawTransfromGroup.Value;

            double dM11 = oMatrix.M11;
            double dM21 = oMatrix.M12;
            double dM12 = oMatrix.M21;
            double dM22 = oMatrix.M22;

            double dXSkew = 0.0, dYSkew = 0.0;
            double dScaleX = 1.0, dScaleY = 1.0;
            double dAngle = 0.0;

            switch (p_eSkewMode)
            {
                case DecompositionSkewMode.XSkewOnly:
                    dXSkew = (dM12 * dM11 + dM21 * dM22) / (dM11 * dM22 - dM12 * dM21);

                    if (dXSkew == 0.0 || double.IsNaN(dXSkew))
                        goto case DecompositionSkewMode.NoSkew;

                    dScaleX = Math.Sqrt(dM11 * dM11 + dM21 * dM21);

                    if (dScaleX != 0.0)
                    {
                        dScaleY = (dM22 * dScaleX) / (dXSkew * dM21 + dM11);
                        dAngle = Math.Atan2(dM21 / dScaleX, dM11 / dScaleX);
                    }
                    break;
                case DecompositionSkewMode.YSkewOnly:
                    dYSkew = (dM12 * dM11 + dM21 * dM22) / (dM11 * dM22 - dM12 * dM21);

                    if (dYSkew == 0.0 || double.IsNaN(dYSkew))
                        goto case DecompositionSkewMode.NoSkew;

                    dScaleY = Math.Sqrt(dM12 * dM12 + dM22 * dM22);

                    if (dScaleY != 0.0)
                    {
                        dScaleX = (dM11 * dScaleY) / (dYSkew * dM12 + dM22);
                        dAngle = Math.Atan2(-dM12 / dScaleY, dM22 / dScaleY);
                    }
                    break;
                case DecompositionSkewMode.NoSkew:
                    dScaleX = Math.Sqrt(dM11 * dM11 + dM21 * dM21);
                    dScaleY = Math.Sqrt(dM12 * dM12 + dM22 * dM22);

                    if (dScaleX != 0.0 && dScaleY != 0.0)
                    {
                        dAngle = Math.Atan2(dM21 / dScaleX, dM22 / dScaleY);
                    }
                    break;
            }

            return (new TransformGroup()
            {
                Children = new TransformCollection()
                {
                    new ScaleTransform()
                    {
                        ScaleX = dScaleX,
                        ScaleY = dScaleY
                    },
                    new SkewTransform()
                    {
                        AngleX = dXSkew,
                        AngleY = dYSkew
                    },
                    new RotateTransform()
                    {
                        Angle = dAngle * 180.0 / Math.PI
                    },
                    new TranslateTransform()
                    {
                        X = oMatrix.OffsetX,
                        Y = oMatrix.OffsetY
                    }
                }
            });
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_ubMinimum">Minimum de <paramref name="p_ubValues" />.</param>
        /// <param name="p_ubMaximum">Maximum de <paramref name="p_ubValues" />.</param>
        /// <param name="p_ubValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_ubValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out byte p_ubMinimum, out byte p_ubMaximum, params byte[] p_ubValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_ubMinimum, out p_ubMaximum, p_ubValues, "p_ubValues"))
                return;

            int iCountValues = p_ubValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                byte ubValue = p_ubValues[i];

                if (ubValue < p_ubMinimum)
                    p_ubMinimum = ubValue;

                if (ubValue > p_ubMaximum)
                    p_ubMaximum = ubValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_cMinimum">Minimum de <paramref name="p_cValues" />.</param>
        /// <param name="p_cMaximum">Maximum de <paramref name="p_cValues" />.</param>
        /// <param name="p_cValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_cValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out decimal p_cMinimum, out decimal p_cMaximum, params decimal[] p_cValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_cMinimum, out p_cMaximum, p_cValues, "p_cValues"))
                return;

            int iCountValues = p_cValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                decimal cValue = p_cValues[i];

                if (cValue < p_cMinimum)
                    p_cMinimum = cValue;

                if (cValue > p_cMaximum)
                    p_cMaximum = cValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_dMinimum">Minimum de <paramref name="p_dValues" />.</param>
        /// <param name="p_dMaximum">Maximum de <paramref name="p_dValues" />.</param>
        /// <param name="p_dValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_dValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out double p_dMinimum, out double p_dMaximum, params double[] p_dValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_dMinimum, out p_dMaximum, p_dValues, "p_dValues"))
                return;

            int iCountValues = p_dValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                double dValue = p_dValues[i];

                if (dValue < p_dMinimum)
                    p_dMinimum = dValue;

                if (dValue > p_dMaximum)
                    p_dMaximum = dValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_fMinimum">Minimum de <paramref name="p_fValues" />.</param>
        /// <param name="p_fMaximum">Maximum de <paramref name="p_fValues" />.</param>
        /// <param name="p_fValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_fValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out float p_fMinimum, out float p_fMaximum, params float[] p_fValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_fMinimum, out p_fMaximum, p_fValues, "p_fValues"))
                return;

            int iCountValues = p_fValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                float fValue = p_fValues[i];

                if (fValue < p_fMinimum)
                    p_fMinimum = fValue;

                if (fValue > p_fMaximum)
                    p_fMaximum = fValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_iMinimum">Minimum de <paramref name="p_iValues" />.</param>
        /// <param name="p_iMaximum">Maximum de <paramref name="p_iValues" />.</param>
        /// <param name="p_iValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_iValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out int p_iMinimum, out int p_iMaximum, params int[] p_iValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_iMinimum, out p_iMaximum, p_iValues, "p_iValues"))
                return;

            int iCountValues = p_iValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                int iValue = p_iValues[i];

                if (iValue < p_iMinimum)
                    p_iMinimum = iValue;

                if (iValue > p_iMaximum)
                    p_iMaximum = iValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_lMinimum">Minimum de <paramref name="p_lValues" />.</param>
        /// <param name="p_lMaximum">Maximum de <paramref name="p_lValues" />.</param>
        /// <param name="p_lValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_lValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out long p_lMinimum, out long p_lMaximum, params long[] p_lValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_lMinimum, out p_lMaximum, p_lValues, "p_lValues"))
                return;

            int iCountValues = p_lValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                long lValue = p_lValues[i];

                if (lValue < p_lMinimum)
                    p_lMinimum = lValue;

                if (lValue > p_lMaximum)
                    p_lMaximum = lValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_sbMinimum">Minimum de <paramref name="p_sbValues" />.</param>
        /// <param name="p_sbMaximum">Maximum de <paramref name="p_sbValues" />.</param>
        /// <param name="p_sbValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_sbValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out sbyte p_sbMinimum, out sbyte p_sbMaximum, params sbyte[] p_sbValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_sbMinimum, out p_sbMaximum, p_sbValues, "p_sbValues"))
                return;

            int iCountValues = p_sbValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                sbyte sbValue = p_sbValues[i];

                if (sbValue < p_sbMinimum)
                    p_sbMinimum = sbValue;

                if (sbValue > p_sbMaximum)
                    p_sbMaximum = sbValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_sMinimum">Minimum de <paramref name="p_sValues" />.</param>
        /// <param name="p_sMaximum">Maximum de <paramref name="p_sValues" />.</param>
        /// <param name="p_sValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_sValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out short p_sMinimum, out short p_sMaximum, params short[] p_sValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_sMinimum, out p_sMaximum, p_sValues, "p_sValues"))
                return;

            int iCountValues = p_sValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                short sValue = p_sValues[i];

                if (sValue < p_sMinimum)
                    p_sMinimum = sValue;

                if (sValue > p_sMaximum)
                    p_sMaximum = sValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_uiMinimum">Minimum de <paramref name="p_uiValues" />.</param>
        /// <param name="p_uiMaximum">Maximum de <paramref name="p_uiValues" />.</param>
        /// <param name="p_uiValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_uiValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out uint p_uiMinimum, out uint p_uiMaximum, params uint[] p_uiValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_uiMinimum, out p_uiMaximum, p_uiValues, "p_uiValues"))
                return;

            int iCountValues = p_uiValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                uint uiValue = p_uiValues[i];

                if (uiValue < p_uiMinimum)
                    p_uiMinimum = uiValue;

                if (uiValue > p_uiMaximum)
                    p_uiMaximum = uiValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_ulMinimum">Minimum de <paramref name="p_ulValues" />.</param>
        /// <param name="p_ulMaximum">Maximum de <paramref name="p_ulValues" />.</param>
        /// <param name="p_ulValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_ulValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out ulong p_ulMinimum, out ulong p_ulMaximum, params ulong[] p_ulValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_ulMinimum, out p_ulMaximum, p_ulValues, "p_ulValues"))
                return;

            int iCountValues = p_ulValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                ulong ulValue = p_ulValues[i];

                if (ulValue < p_ulMinimum)
                    p_ulMinimum = ulValue;

                if (ulValue > p_ulMaximum)
                    p_ulMaximum = ulValue;
            }
        }

        /// <summary>
        /// Calcul le minimum et le maximum des valeurs spécifiés.
        /// </summary>
        /// <param name="p_usMinimum">Minimum de <paramref name="p_usValues" />.</param>
        /// <param name="p_usMaximum">Maximum de <paramref name="p_usValues" />.</param>
        /// <param name="p_usValues">Valeurs dont on veut le minimum et le maximum.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_usValues" /> est <see langword="null" />.</exception>
        public static void GetMinMax(out ushort p_usMinimum, out ushort p_usMaximum, params ushort[] p_usValues)
        {
            if (!MathHelper.CheckMinMaxParameters(out p_usMinimum, out p_usMaximum, p_usValues, "p_usValues"))
                return;

            int iCountValues = p_usValues.Length;

            for (int i = 1; i < iCountValues; i++)
            {
                ushort usValue = p_usValues[i];

                if (usValue < p_usMinimum)
                    p_usMinimum = usValue;

                if (usValue > p_usMaximum)
                    p_usMaximum = usValue;
            }
        }

        #endregion

        #region Fonctions statiques privées

        /// <summary>
        /// Vérifie les paramètres de calcul du minimum et du maximum.
        /// </summary>
        /// <typeparam name="T">Type des paramètres.</typeparam>
        /// <param name="p_tMinimum">Minimum par défaut.</param>
        /// <param name="p_tMaximum">Maximum par défaut.</param>
        /// <param name="p_tValues">Valeurs dont on veut le minimum et le maximum par défaut.</param>
        /// <param name="p_oValuesName">Nom du paramètre <paramref name="p_tValues" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_tValues" /> est <see langword="null" />.</exception>
        /// <returns><see langword="true" /> s'il faut faire le calcul, <see langword="false" /> sinon.</returns>
        private static bool CheckMinMaxParameters<T>(out T p_tMinimum, out T p_tMaximum, T[] p_tValues, string p_oValuesName)
        {
            if (p_tValues == null)
                throw new ArgumentNullException(p_oValuesName);

            if (p_tValues.Length == 0)
            {
                p_tMinimum = default(T);
                p_tMaximum = default(T);
                return (false);
            }

            p_tMinimum = p_tValues[0];
            p_tMaximum = p_tValues[0];

            return (true);
        }

        #endregion
    }
}
